import maya.cmds as cmds
import maya.OpenMaya as om
import mtoa.ui.ae.lightTemplate as lightTemplate
from mtoa.ui.ae.utils import aeCallback, AttrControlGrp
import mtoa.ui.ae.templates as templates
import mtoa.callbacks as callbacks
import mtoa.melUtils as melUtils
import mtoa.core as core
import mtoa.utils as utils
import re
import mtoa.aovs as aovs
import arnold.ai_ray as ai_ray


class ParticleTemplate(templates.ShapeTranslatorTemplate):
    def setup(self):
        self.commonShapeAttributes()
        self.addControl("aiRenderPointsAs", label="Render Points As")
        self.addControl("aiMinParticleRadius", label="Min Particle Radius")
        self.addControl("aiRadiusMultiplier", label="Radius Multiplier")
        self.addControl("aiMaxParticleRadius", label="Max Particle Radius")
        self.addControl("aiMinPixelWidth", label="Min Pixel Width") # Not supported in GPU
        self.addSeparator()   
        self.addControl("aiFalloffExponent", label="Falloff Exponent")
        self.addControl("aiSmoothStepFalloff", label="Smoothstep Falloff")
        self.addControl("aiImplicitSamples", label="Implicit Samples")
        self.addControl('aiStepSize', label="Volume Step Size")
        self.addControl('aiStepScale', label="Volume Step Scale")
        self.addSeparator()
        self.addControl("aiExportParticleIDs", label="Export Particle Id")
        self.addControl("aiExportAttributes", label="Export Attributes")
        self.addSeparator()
        self.addControl("aiDeleteDeadParticles", label="Delete Dead Particles")
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")
        
if utils.nodeTypeExists('particle'):
    templates.registerTranslatorUI(ParticleTemplate, "particle", "<built-in>")

class NParticleTemplate(templates.ShapeTranslatorTemplate):
    def setup(self):
        self.commonShapeAttributes()
        self.addControl("aiRenderPointsAs", label="Render Points As")
        self.addControl("aiMinParticleRadius", label="Min Particle Radius")
        self.addControl("aiRadiusMultiplier", label="Radius Multiplier")
        self.addControl("aiMaxParticleRadius", label="Max Particle Radius")
        self.addControl("aiMinPixelWidth", label="Min Pixel Width") # Not supported in GPU
        self.addSeparator()   
        self.addControl("aiFalloffExponent", label="Falloff Exponent")
        self.addControl("aiSmoothStepFalloff", label="Smoothstep Falloff")
        self.addControl("aiImplicitSamples", label="Implicit Samples")
        self.addControl('aiStepSize', label="Volume Step Size")
        self.addControl('aiStepScale', label="Volume Step Scale")
        self.addSeparator()
        self.addControl("aiExportParticleIDs", label="Export Particle Id")
        self.addControl("aiExportAttributes", label="Export Attributes")
        self.addSeparator()
        self.addControl("aiInterpolateBlur", label="Interpolated Motion Blur")
        self.addControl("aiEvaluateEvery", label="nCache Evaluation Interval", annotation="Use nCache's \"Evaluate every # frame(s)\" param value")
        self.addSeparator()
        self.addControl("aiDeleteDeadParticles", label="Delete Dead Particles")
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

if utils.nodeTypeExists('nParticle'):
    templates.registerTranslatorUI(NParticleTemplate, "nParticle", "<built-in>")

class MeshTemplate(templates.ShapeTranslatorTemplate):

    def updateSubdiv(self, *args):
        enabled = True if (cmds.getAttr(self.nodeAttr('aiSubdivType')) > 0) else False
        self.aiSubdivIterationsCtrl.edit(enable=enabled)
        self.aiSubdivAdaptiveMetricCtrl.edit(enable=enabled)
        self.aiSubdivPixelErrorCtrl.edit(enable=enabled)
        self.aiSubdivAdaptiveSpaceCtrl.edit(enable=enabled)
        self.aiSubdivUvSmoothingCtrl.edit(enable=enabled)
        self.aiSubdivSmoothDerivsCtrl.edit(enable=enabled)
        self.aiSubdivFrustumIgnoreCtrl.edit(enable=enabled)

    def subdivNew(self, nodeAttr):

        cmds.setUITemplate('attributeEditorTemplate', pst=True)
        self.aiSubdivIterationsCtrl = AttrControlGrp(self.nodeAttr("aiSubdivIterations"), label="Iterations")
        self.aiSubdivAdaptiveMetricCtrl = AttrControlGrp(self.nodeAttr("aiSubdivAdaptiveMetric"), label="Adaptive Metric")
        self.aiSubdivPixelErrorCtrl = AttrControlGrp(self.nodeAttr("aiSubdivPixelError"), label="Adaptive Error")
        self.aiSubdivAdaptiveSpaceCtrl = AttrControlGrp(self.nodeAttr("aiSubdivAdaptiveSpace"), label="Adaptive Space")
        self.aiSubdivUvSmoothingCtrl = AttrControlGrp(self.nodeAttr("aiSubdivUvSmoothing"), label="UV Smoothing")
        self.aiSubdivSmoothDerivsCtrl = AttrControlGrp(self.nodeAttr("aiSubdivSmoothDerivs"), label="Smooth Tangents")
        self.aiSubdivFrustumIgnoreCtrl = AttrControlGrp(self.nodeAttr("aiSubdivFrustumIgnore"), label="Ignore Frustum Culling")
        cmds.setUITemplate(ppt=True)
        self.subdivReplace(nodeAttr)

    def subdivReplace(self, nodeAttr):
        self.aiSubdivIterationsCtrl.setAttribute(self.nodeAttr('aiSubdivIterations'))
        self.aiSubdivAdaptiveMetricCtrl.setAttribute(self.nodeAttr('aiSubdivAdaptiveMetric'))
        self.aiSubdivPixelErrorCtrl.setAttribute(self.nodeAttr('aiSubdivPixelError'))
        self.aiSubdivAdaptiveSpaceCtrl.setAttribute(self.nodeAttr('aiSubdivAdaptiveSpace'))
        self.aiSubdivUvSmoothingCtrl.setAttribute(self.nodeAttr('aiSubdivUvSmoothing'))
        self.aiSubdivSmoothDerivsCtrl.setAttribute(self.nodeAttr('aiSubdivSmoothDerivs'))
        self.aiSubdivFrustumIgnoreCtrl.setAttribute(self.nodeAttr('aiSubdivFrustumIgnore'))
        self.updateSubdiv()

    def updateAutobump(self, nodeAttr, *args):
        enabled = cmds.checkBox(self.aiAutobumpCtrl, q=True, value=True)
        previousEnable = True if cmds.getAttr(nodeAttr) else False
        # only call setAttr if the value is actually different, or
        # this will trigger an IPR refresh #4344
        if previousEnable != enabled:
            cmds.setAttr(nodeAttr, enabled)
        cmds.checkBox(self.aiAutobumpCamCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpDiffReflCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpDiffTransCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpSpecReflCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpSpecTransCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpVolCtrl, edit=True, enable = enabled)
        cmds.checkBox(self.aiAutobumpSssCtrl, edit=True, enable = enabled)

    def autobumpVisibilityChanged(self, nodeAttr, *args):
        vis = 0
        if cmds.checkBox(self.aiAutobumpCamCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_CAMERA
        if cmds.checkBox(self.aiAutobumpDiffReflCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_DIFFUSE_REFLECT
        if cmds.checkBox(self.aiAutobumpDiffTransCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_DIFFUSE_TRANSMIT
        if cmds.checkBox(self.aiAutobumpSpecReflCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_SPECULAR_REFLECT
        if cmds.checkBox(self.aiAutobumpSpecTransCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_SPECULAR_TRANSMIT
        if cmds.checkBox(self.aiAutobumpVolCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_VOLUME
        if cmds.checkBox(self.aiAutobumpSssCtrl, q=True, v=True):
            vis += ai_ray.AI_RAY_SUBSURFACE

        cmds.setAttr(nodeAttr, vis)

    def autobumpNew(self, nodeAttr):

        cmds.setUITemplate('attributeEditorTemplate', pst=True)
        cmds.rowLayout( numberOfColumns=1, columnAlign=[(1, 'center')] )
        self.aiAutobumpCtrl = cmds.checkBox("aiDispAutobump", label="Enable Autobump",
            changeCommand=lambda *args: self.updateAutobump(nodeAttr, *args))
        cmds.setParent('..')
        
        cmds.rowColumnLayout( numberOfColumns=2, columnAlign=[(1, 'left'),(2, 'left')], columnAttach=[(1, 'left', 0), (2, 'left', 0)], columnWidth=[(1,200),(2,200)] )
        self.aiAutobumpCamCtrl = cmds.checkBox('aiAutobumpCamera', label="Camera (primary)")
        self.aiAutobumpDiffReflCtrl = cmds.checkBox('aiAutobumpDiffuseReflection', label="Diffuse Reflection")
        self.aiAutobumpDiffTransCtrl = cmds.checkBox('aiAutobumpDiffuseTransmission', label="Diffuse Transmission")
        self.aiAutobumpSpecReflCtrl = cmds.checkBox('aiAutobumpSpecularReflection', label="Specular Reflection")
        self.aiAutobumpSpecTransCtrl = cmds.checkBox('aiAutobumpSpecularTransmission', label="Specular Transmission")
        self.aiAutobumpVolCtrl = cmds.checkBox('aiAutobumpVolumeScattering', label="Volume Scattering")
        self.aiAutobumpSssCtrl = cmds.checkBox('aiAutobumpSubsurfaceScattering', label="Subsurface Scattering")
        cmds.setParent('..')
        cmds.setUITemplate(ppt=True)
        self.autobumpReplace(nodeAttr)
        
    def autobumpReplace(self, nodeAttr):
        cmds.checkBox(self.aiAutobumpCtrl, edit=True,
                      changeCommand=lambda *args: self.updateAutobump(nodeAttr, *args))
        cmds.checkBox(self.aiAutobumpCtrl, edit=True,
                      value=cmds.getAttr(nodeAttr))
        self.updateAutobump(nodeAttr)
        
        visAttr = nodeAttr.replace('aiDispAutobump', 'aiAutobumpVisibility')
        vis = cmds.getAttr(visAttr)
        cmds.checkBox(self.aiAutobumpCamCtrl, edit=True, value = (vis & ai_ray.AI_RAY_CAMERA) != 0)
        cmds.checkBox(self.aiAutobumpDiffReflCtrl, edit=True, value = (vis & ai_ray.AI_RAY_DIFFUSE_REFLECT) != 0)
        cmds.checkBox(self.aiAutobumpDiffTransCtrl, edit=True, value = (vis & ai_ray.AI_RAY_DIFFUSE_TRANSMIT) != 0)
        cmds.checkBox(self.aiAutobumpSpecReflCtrl, edit=True, value = (vis & ai_ray.AI_RAY_SPECULAR_REFLECT) != 0)
        cmds.checkBox(self.aiAutobumpSpecTransCtrl, edit=True, value = (vis & ai_ray.AI_RAY_SPECULAR_TRANSMIT) != 0)
        cmds.checkBox(self.aiAutobumpVolCtrl, edit=True, value = (vis & ai_ray.AI_RAY_VOLUME) != 0)
        cmds.checkBox(self.aiAutobumpSssCtrl, edit=True, value = (vis & ai_ray.AI_RAY_SUBSURFACE) != 0)
        
        cmds.checkBox(self.aiAutobumpCamCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpDiffReflCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpDiffTransCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpSpecReflCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpSpecTransCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpVolCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))
        cmds.checkBox(self.aiAutobumpSssCtrl, edit=True, changeCommand=lambda *args: self.autobumpVisibilityChanged(visAttr, *args))

        
    def setup(self):
        self.commonShapeAttributes()        
        self.beginLayout("Export", collapse=False)
        self.addControl("aiExportTangents", label="Export Tangents")
        self.addControl("aiExportColors", label="Export Vertex Colors")
        self.addControl("aiExportRefPoints", label="Export Reference Positions")
        self.addControl("aiExportRefNormals", label="Export Reference Normals")
        self.addControl("aiExportRefTangents", label="Export Reference Tangents")
        
        self.addSeparator()
        self.addControl("aiSssSetname", label="SSS Set Name")
        self.addControl("aiToonId", label="Toon ID")

        self.addSeparator()
        self.addControl("aiMotionVectorSource", label="Motion Vector Source")
        self.addControl("aiMotionVectorUnit", label="Motion Vector Unit")
        self.addControl("aiMotionVectorScale", label="Motion Vector Scale")

        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")
        self.endLayout()
        
        self.beginLayout('Subdivision', collapse=True)
        self.addControl("aiSubdivType", label="Type", changeCommand=self.updateSubdiv)

        self.aiSubdivAdaptiveMetricCtrl = ""
        self.aiSubdivIterationsCtrl = ""
        self.aiSubdivPixelErrorCtrl = ""
        self.aiSubdivAdaptiveSpaceCtrl = ""
        self.aiSubdivUvSmoothingCtrl = ""
        self.aiSubdivSmoothDerivsCtrl = ""
        self.aiSubdivFrustumIgnoreCtrl = ""
        self.addCustom("aiSubdivIterations", self.subdivNew, self.subdivReplace)
        
        self.endLayout()
        
        self.beginLayout('Displacement Attributes', collapse=True)
        self.addControl("aiDispHeight", label="Height")
        self.addControl("aiDispPadding", label="Bounds Padding")
        self.addControl("aiDispZeroValue", label="Scalar Zero Value")

        self.beginLayout('Autobump', collapse=True)
        self.addCustom('aiDispAutobump', self.autobumpNew, self.autobumpReplace)
        self.endLayout()
        
        self.endLayout()
        self.beginLayout('Volume Attributes', collapse=True)
        self.addControl('aiStepSize', label='Step Size')
        self.addControl('aiVolumePadding', label='Volume Padding *') # Not supported in GPU
        self.endLayout()
        
        # FIXME: these are not on the shape node!
#       ui.addSeparator()
#       ui.addControl("enableProcedural")
#       ui.addControl("dso")


def LoadProceduralButtonPush(nodeName):
    basicFilter = 'Arnold Archive (*.ass *.ass.gz *.obj *.ply);;Arnold Procedural (*.so *.dll *.dylib)'
    projectDir = cmds.workspace(query=True, directory=True)     
    ret = cmds.fileDialog2(fileFilter=basicFilter, cap='Load StandIn',okc='Load',fm=1, startingDirectory=projectDir)
    if ret is not None and len(ret):
        ProceduralDsoEdit(nodeName, ret[0], True)

def ProceduralDsoEdit(nodeName, mPath, replace=False) :
    mArchivePath = ''
    nodeName = nodeName.replace('.dso','')
    
    expression = r''
    if replace:
        # Expression to replace frame numbers by #
        expression = r'(.*?)([\._])([0-9#]*)([\.]?)([0-9#]*)(\.ass\.gz|\.ass|\.obj|\.ply)$'
    else:
        expression = r'(.*?)([\._])([#]*)([\.]?)([#]*)(\.ass\.gz|\.ass|\.obj|\.ply)$'

    # If it is a recogniced format
    if re.search(expression,mPath) != None:
        m_groups = re.search(expression,mPath).groups()

        # Single file
        if not m_groups[2]:
            mArchivePath = mPath
            cmds.setAttr(nodeName+'.aiUseFrameExtension',False)
        # Sequence without subframes    
        elif not m_groups[3]:
            cmds.setAttr(nodeName+'.aiUseFrameExtension',True)
            mArchivePath = m_groups[0]+m_groups[1]+'#'*len(m_groups[2])+m_groups[5]
            cmds.setAttr(nodeName+'.aiUseSubFrame',False)
            cmds.setAttr(nodeName+'.aiFrameNumber', int(m_groups[2])) 
        # Sequence with subframes
        else:
            cmds.setAttr(nodeName+'.aiUseFrameExtension',True)
            mArchivePath = m_groups[0]+m_groups[1]+'#'*len(m_groups[2])+m_groups[3]+'#'*len(m_groups[4])+m_groups[5]
            cmds.setAttr(nodeName+'.aiUseSubFrame',True)
    # Other
    else:
        mArchivePath = mPath

    cmds.setAttr(nodeName+'.dso',mArchivePath,type='string')
    cmds.textField('proceduralDsoPath', edit=True, text=mArchivePath)

def ProceduralTemplateDsoNew(nodeName) :
    cmds.rowColumnLayout( numberOfColumns=3, columnAlign=[(1, 'right'),(2, 'right'),(3, 'left')], columnAttach=[(1, 'right', 0), (2, 'both', 0), (3, 'left', 5)], columnWidth=[(1,145),(2,220),(3,30)] )
    cmds.text(label='Path ')
    path = cmds.textField('proceduralDsoPath',changeCommand=lambda *args: ProceduralDsoEdit(nodeName, *args))
    cmds.textField( path, edit=True, text=cmds.getAttr(nodeName) )
    cmds.symbolButton('ProceduralPathButton', image='navButtonBrowse.png', command=lambda arg=None,x=nodeName: LoadProceduralButtonPush(x))

def ProceduralTemplateDsoReplace(plugName) :
    cmds.textField( 'proceduralDsoPath', edit=True, changeCommand=lambda *args: ProceduralDsoEdit(plugName, *args))
    cmds.textField( 'proceduralDsoPath', edit=True, text=cmds.getAttr(plugName) )
    cmds.symbolButton('ProceduralPathButton', edit=True, image='navButtonBrowse.png' , command=lambda arg=None,x=plugName: LoadProceduralButtonPush(x))

class ProceduralTemplate(templates.ShapeTranslatorTemplate):

    def setup(self):

        self.beginLayout('File/Frame', collapse=False)   
        self.addCustom('dso', ProceduralTemplateDsoNew, ProceduralTemplateDsoReplace)

        self.addSeparator()
        self.addControl('aiFrameNumber', label='Frame')
        self.addControl('aiFrameOffset')

        self.endLayout()

        self.beginLayout('StandIn Overrides', collapse=False)
        self.addControl('aiOverrideLightLinking', label='Override StandIn Light Linking')
        self.addControl('aiOverrideShaders', label='Override StandIn Shaders')
        self.addSeparator()
        self.addOverrideAttributes()
        # self.addControl('aiOverrideReceiveShadows', changeCommand=self.updateOverridesVisibility, label='Override Receive Shadows')
        # self.addControl('receiveShadows', label='   Receive Shadows')
        # self.addControl('aiOverrideSelfShadows', changeCommand=self.updateOverridesVisibility,  label='Override Self Shadows')
        # self.addControl('aiSelfShadows', label='   Self Shadows')
        # self.addControl('aiOverrideOpaque', changeCommand=self.updateOverridesVisibility, label='Override Opaque')
        # self.addControl('aiOpaque', label='   Opaque')
        # self.addControl('aiOverrideDoubleSided', changeCommand=self.updateOverridesVisibility,  label='Override Double-Sided')
        # self.addControl('doubleSided', label='   Double-Sided')
        # self.addControl('aiOverrideMatte', changeCommand=self.updateOverridesVisibility, label='Override Matte')
        # self.addControl('aiMatte', label='   Matte')
        self.endLayout()
        
        self.beginLayout("Visibility", collapse=False)
        self.addControl("primaryVisibility", label="Primary Visibility")
        self.addControl("castsShadows", label="Casts Shadows")
        self.addControl("aiVisibleInDiffuseReflection", label="Diffuse Reflection")
        self.addControl("aiVisibleInSpecularReflection", label="Specular Reflection")
        self.addControl("aiVisibleInDiffuseTransmission", label="Diffuse Transmission")
        self.addControl("aiVisibleInSpecularTransmission", label="Specular Transmission")
        self.addControl("aiVisibleInVolume", label="Volume")
        self.addControl("aiSelfShadows", label="Self Shadows")
        self.addControl("aiTraceSets", label="Trace Sets")
        self.endLayout()        

#        self.addControl('deferStandinLoad', label='Defer Procedural Load')
        self.addControl('aiOverrideNodes')
        self.addControl('aiNamespace')
        self.addControl("aiUserOptions", label="User Options")
        
    def addOverrideAttributes(self):

        self.beginNoOptimize()
        self.receiveShadowsCtrl = ""
        self.aiSelfShadowsCtrl = ""
        self.aiOpaquesCtrl = ""
        self.doubleSidedCtrl = ""
        self.aiMatteCtrl = ""
        self.addCustom("aiOverrideReceiveShadows", self.overridesNew, self.overridesReplace)
        self.endNoOptimize()

    def overridesChanged(self, nodeAttr, control, enabled):
        cmds.setAttr(nodeAttr, enabled)
        if control:
            cmds.checkBox(control, edit=True, enable=enabled)

    def overridesNew(self, nodeAttr):

        cmds.setUITemplate('attributeEditorTemplate', pst=True)

        cmds.rowLayout( numberOfColumns=1, columnAlign=[(1, 'center')] )
        cmds.columnLayout(columnAlign='center')

        self.aiOverrideReceiveShadowsCtrl = cmds.checkBox('aiOverrideReceiveShadowsCtrl', label="Override Receive Shadows")
        self.receiveShadowsCtrl = cmds.checkBox("receiveShadowsCtrl", label=" Receive Shadows")

        self.aiOverrideSelfShadowsCtrl = cmds.checkBox('aiOverrideSelfShadowsCtrl', label="Override Self Shadows")
        self.aiSelfShadowsCtrl = cmds.checkBox("aiSelfShadowsCtrl", label=" Self Shadows")

        self.aiOverrideOpaqueCtrl = cmds.checkBox('aiOverrideOpaqueCtrl', label="Override Opaque")
        self.aiOpaquesCtrl = cmds.checkBox("aiOpaqueCtrl", label=" Opaque")

        self.aiOverrideDoubleSidedCtrl = cmds.checkBox('aiOverrideDoubleSidedCtrl', label='Override Double-Sided')
        self.doubleSidedCtrl = cmds.checkBox('doubleSidedCtrl', label='   Double-Sided')

        self.aiOverrideMatteCtrl = cmds.checkBox('aiOverrideMatteCtrl', label='Override Matte')
        self.aiMatteCtrl = cmds.checkBox('aiMatteCtrl', label='   Matte')

        cmds.setParent("..")
        cmds.setParent("..")

        cmds.setUITemplate(ppt=True)

        self.overridesReplace(nodeAttr)

    def overridesReplace(self, nodeAttr):

        checkattra = self.nodeAttr('aiOverrideReceiveShadows')
        cmds.checkBox(self.aiOverrideReceiveShadowsCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(checkattra, self.receiveShadowsCtrl, x),
                      value=bool(cmds.getAttr(checkattra)))

        attra = self.nodeAttr('receiveShadows')
        cmds.checkBox(self.receiveShadowsCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(attra, None, x),
                      enable=bool(cmds.getAttr(checkattra)),
                      value=bool(cmds.getAttr(attra)))

        checkattrb = self.nodeAttr('aiOverrideSelfShadows')
        cmds.checkBox(self.aiOverrideSelfShadowsCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(checkattrb, self.aiSelfShadowsCtrl, x),
                      value=bool(cmds.getAttr(checkattrb)))

        attrb = self.nodeAttr('aiSelfShadows')
        cmds.checkBox(self.aiSelfShadowsCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(attrb, None, x),
                      enable=bool(cmds.getAttr(checkattrb)),
                      value=bool(cmds.getAttr(attrb)))

        checkattrc = self.nodeAttr('aiOverrideOpaque')
        cmds.checkBox(self.aiOverrideOpaqueCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(checkattrc, self.aiOpaquesCtrl, x),
                      value=bool(cmds.getAttr(checkattrc)))

        attrc = self.nodeAttr('aiOpaque')
        cmds.checkBox(self.aiOpaquesCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(attrc, None, x),
                      enable=bool(cmds.getAttr(checkattrc)),
                      value=bool(cmds.getAttr(attrc)))

        checkattrd = self.nodeAttr('aiOverrideDoubleSided')
        cmds.checkBox(self.aiOverrideDoubleSidedCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(checkattrd, self.doubleSidedCtrl, x),
                      value=bool(cmds.getAttr(checkattrd)))

        attrd = self.nodeAttr('doubleSided')
        cmds.checkBox(self.doubleSidedCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(attrd, None, x),
                      enable=bool(cmds.getAttr(checkattrd)),
                      value=bool(cmds.getAttr(attrd)))

        checkattre = self.nodeAttr('aiOverrideMatte')
        cmds.checkBox(self.aiOverrideMatteCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(checkattre, self.aiMatteCtrl, x),
                      value=bool(cmds.getAttr(checkattre)))

        attre = self.nodeAttr('aiMatte')
        cmds.checkBox(self.aiMatteCtrl, edit=True,
                      changeCommand=lambda x: self.overridesChanged(attre, None, x),
                      enable=bool(cmds.getAttr(checkattre)),
                      value=bool(cmds.getAttr(attre)))
  

templates.registerTranslatorUI(MeshTemplate, "mesh", "polymesh")
templates.registerTranslatorUI(ProceduralTemplate, "mesh", "procedural")
templates.registerTranslatorUI(MeshTemplate, "nurbsSurface", "<built-in>")

class HairSystemTemplate(templates.ShapeTranslatorTemplate):
    def shaderCreate(self, attrName):
        cmds.setUITemplate('attributeEditorPresetsTemplate', pushTemplate=True)
        cmds.attrNavigationControlGrp("HairSystemTemplateShader", attribute=attrName, label="Hair Shader")
        cmds.setUITemplate(popTemplate=True)

    def shaderUpdate(self, attrName):
        cmds.attrNavigationControlGrp("HairSystemTemplateShader", edit=True, attribute=attrName)

    def minPixelCreate(self, attrName):
        cmds.setUITemplate('attributeEditorPresetsTemplate', pushTemplate=True)
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("HairTemplateMinPixelWidth", label="Min Pixel Width",
                            attribute=attrName, enable=isEnabled)
        cmds.setUITemplate(popTemplate=True)
    
    def minPixelUpdate(self, attrName):
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("HairTemplateMinPixelWidth", edit=True,
                            attribute=attrName, enable=isEnabled)

    def indirectDiffuseCreate(self, attrName):
        cmds.setUITemplate('attributeEditorPresetsTemplate', pushTemplate=True)
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("HairTemplateIndirectDiffuse", label="Indirect Diffuse",
                            attribute=attrName, enable=isEnabled)
        cmds.setUITemplate(popTemplate=True)
    
    def indirectDiffuseUpdate(self, attrName):
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("HairTemplateIndirectDiffuse", edit=True,
                            attribute=attrName, enable=isEnabled)

    def modeChanged(self, *args):
        try:
            if cmds.getAttr(self.nodeAttr('aiMode')) == 1:
                cmds.attrFieldSliderGrp("HairTemplateMinPixelWidth", edit=True, enable=False)
            else:
                cmds.attrFieldSliderGrp("HairTemplateMinPixelWidth", edit=True, enable=True)
        except RuntimeError:
            # this callback runs immediately, before HairTemplateMinPixelWidth exists
            pass

    def setup(self):
        self.addControl("primaryVisibility")
        self.addControl("castsShadows")
        self.addSeparator()
        self.commonShapeAttributes()
        self.addSeparator()
        self.addControl("aiExportHairUVs", label="Export Hair UVs")
        self.addControl("aiExportHairColors", label="Export Hair Colors")
        self.addControl("aiOverrideHair", label="Override Hair")
        self.addCustom("aiHairShader", self.shaderCreate, self.shaderUpdate)
        self.addSeparator()
        self.addCustom("aiMinPixelWidth", self.minPixelCreate, self.minPixelUpdate)
        self.addCustom("aiIndirectDiffuse", self.indirectDiffuseCreate, self.indirectDiffuseUpdate)
        self.addControl("aiMode", label="Mode", changeCommand=self.modeChanged)
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")
templates.registerAETemplate(HairSystemTemplate, "hairSystem")

class FLuidShapeTemplate(templates.ShapeTranslatorTemplate):
    def volumeNoiseCreate(self, attrName):
        cmds.setUITemplate('attributeEditorPresetsTemplate', pushTemplate=True)
        cmds.attrNavigationControlGrp("FluidTemplateVolumeTexture", attribute=attrName, label="Texture")
        cmds.setUITemplate(popTemplate=True)

    def volumeNoiseUpdate(self, attrName):
        cmds.attrNavigationControlGrp("FluidTemplateVolumeTexture", edit=True, attribute=attrName)
        
    def setup(self):
        self.addControl("aiStepSize", label="Step Size")
        self.addControl("aiEnableDeformationBlur", label="Enable Deformation Blur")
        self.addControl("aiMotionVectorScale", label="Motion Vector Scale")
        self.addControl("aiFilterType", label="Filter Type")
        self.addControl("aiPhaseFunc", label="Phase Function Anisotropy")
        self.addSeparator()
        self.commonShapeAttributes()
        self.beginLayout("Custom Texture", collapse=False)
        self.addControl("aiOverrideTextures", label="Override Fluid Texture")        
        self.addControl("aiTextureAffectColor", label="Texture Color")
        self.addControl("aiTextureAffectIncand", label="Texture Incandescence")
        self.addControl("aiTextureAffectOpacity", label="Texture Opacity")
        self.addControl("aiTextureCoordinateMethod", label="Coordinate Method")
        self.addCustom("aiVolumeTexture", self.volumeNoiseCreate, self.volumeNoiseUpdate)
        self.endLayout()
        self.addControl("aiUserOptions", label="User Options")
templates.registerAETemplate(FLuidShapeTemplate, "fluidShape")

class NurbsCurveTemplate(templates.ShapeTranslatorTemplate):
    def minPixelCreate(self, attrName):
        cmds.setUITemplate('attributeEditorPresetsTemplate', pushTemplate=True)
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("NurbsCurveTemplateMinPixelWidth", label="Min Pixel Width",
                            attribute=attrName, enable=isEnabled)
        cmds.setUITemplate(popTemplate=True)
    
    def minPixelUpdate(self, attrName):
        isEnabled = cmds.getAttr("%s.aiMode" % (attrName.split(".")[0])) != 1
        cmds.attrFieldSliderGrp("NurbsCurveTemplateMinPixelWidth", edit=True,
                            attribute=attrName, enable=isEnabled)
    
    def modeChanged(self, *args):
        try:
            if cmds.getAttr(self.nodeAttr('aiMode')) == 1:
                cmds.attrFieldSliderGrp("NurbsCurveTemplateMinPixelWidth", edit=True, enable=False)
            else:
                cmds.attrFieldSliderGrp("NurbsCurveTemplateMinPixelWidth", edit=True, enable=True)
        except RuntimeError:
            # this callback runs immediately, before NurbsCurveTemplateMinPixelWidth exists
            pass
            
    def setup(self):
        self.addControl("aiRenderCurve")
        self.addControl("aiCurveWidth")
        self.addControl("aiSampleRate")
        self.addControl("aiCurveShader")
        self.addSeparator()
        self.addControl("primaryVisibility")
        self.addControl("castsShadows")
        self.addSeparator()
        self.addControl("aiExportRefPoints", "Export Reference Points")
        self.addSeparator()
        self.commonShapeAttributes()
        self.addSeparator()
        self.addCustom("aiMinPixelWidth", self.minPixelCreate, self.minPixelUpdate)
        self.addControl("aiMode", label="Mode", changeCommand=self.modeChanged)
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")
templates.registerTranslatorUI(NurbsCurveTemplate, "nurbsCurve", "<built-in>")


class DirectionalLightTemplate(lightTemplate.LightTemplate):
    def setup(self):
        self.setupColorTemperature("Directional")
        self.addControl("aiExposure")
        self.addControl("aiAngle")
        
        self.addSeparator()
        
        self.addControl("aiSamples")
        self.addControl("aiNormalize")
        
        self.addSeparator()
        
        self.addControl("aiCastShadows")
        self.addControl("aiShadowDensity")
        
        self.addSeparator()                
        self.commonLightAttributes()

templates.registerTranslatorUI(DirectionalLightTemplate, "directionalLight")

class PointLightTemplate(lightTemplate.LightTemplate):
    def setup(self):
        self.setupColorTemperature("Point")
        self.addControl("aiExposure")
        
        self.addSeparator()
        
        self.addControl("aiSamples")
        self.addControl("aiRadius")
        self.addControl("aiNormalize")

        self.addSeparator()

        self.addControl("aiCastShadows")
        self.addControl("aiShadowDensity")

        self.addSeparator()

        self.commonLightAttributes(addVisibility=True)

templates.registerTranslatorUI(PointLightTemplate, "pointLight")

class SpotLightTemplate(lightTemplate.LightTemplate):
    def setup(self):
        self.setupColorTemperature("Spot")
        self.addControl("aiExposure")
        
        self.addSeparator()
                        
        self.addControl("aiSamples")
        self.addControl("aiRadius")
        self.addControl("aiNormalize")
        self.addControl("aiRoundness")
        
        self.addSeparator()

        self.addControl("aiCastShadows")
        self.addControl("aiShadowDensity")

        self.addSeparator()

        self.addControl("aiAspectRatio")
        self.addControl("aiLensRadius")

        self.addSeparator()

        self.commonLightAttributes()

templates.registerTranslatorUI(SpotLightTemplate, "spotLight")

class AreaLightTemplate(lightTemplate.LightTemplate):
    def setup(self):
        self.setupColorTemperature("Area")
        self.addControl("aiExposure")
        
        self.addSeparator()
        
        self.addControl("aiSamples")
        self.addControl("aiNormalize")

        self.addSeparator()

        self.addControl("aiCastShadows")
        self.addControl("aiShadowDensity")

        self.addSeparator()

        self.addControl("aiResolution")
        self.addControl("aiSpread")
                
        self.addSeparator()

        self.addControl("aiRoundness")
        self.addControl("aiSoftEdge")

        self.addSeparator()

        self.commonLightAttributes(addVisibility=True)

templates.registerTranslatorUI(AreaLightTemplate, "areaLight")

templates.registerAETemplate(templates.TranslatorControl, "camera", label="Camera Type")

class CameraTemplate(templates.AttributeTemplate):
    def syncAttribute(self, attr, control, valueField, positionField):
        attr = self.nodeAttr('aiShutterCurve')
        values = cmds.gradientControlNoAttr( control, query=True, asString=True) 
        valuesSplit = values.split(',')

        points = []

        for i in range(0,len(valuesSplit)/3):
            points.append([valuesSplit[i*3+1],valuesSplit[i*3],0])
            
        current = cmds.gradientControlNoAttr( control, query=True, currentKey=True) 
        cmds.floatField(valueField, edit=True, value=float(points[current][1]))
        cmds.floatField(positionField, edit=True, value=float(points[current][0]))
        points[current][2] = 1
        points.sort()
        
        size = melUtils.getAttrNumElements(*attr.split('.', 1))
        for i in range(0,size):
            cmds.removeMultiInstance(attr+'['+str(i)+']')
        
        curveString = ""
        for i in range(0,len(points)):
            cmds.setAttr(attr+'['+str(i)+'].aiShutterCurveX',float(points[i][0]))
            cmds.setAttr(attr+'['+str(i)+'].aiShutterCurveY',float(points[i][1]))
            if i == 0:
                curveString += points[i][1] +"," + points[i][0] +",1"
            else:
                curveString += ","+points[i][1] +"," + points[i][0] +",1"
            
        # We save the curve points sorted in the attribute, so we will also resort the points in
        #  the gradient control
        current = [x[2] for x in points].index(1)
        cmds.gradientControlNoAttr( control, edit=True, currentKey=current, asString=curveString) 
            
    def updateValue(self, attr, control, valueField, positionField):
        value = cmds.floatField(valueField, query=True, value=True)
        
        values = cmds.gradientControlNoAttr( control, query=True, asString=True) 
        valuesSplit = values.split(',')
            
        current = cmds.gradientControlNoAttr( control, query=True, currentKey=True) 
        
        valuesSplit[current*3] = str(value)
        values = ",".join(valuesSplit)
        
        cmds.gradientControlNoAttr( control, edit=True, asString=values)
        self.syncAttribute(attr, control, valueField, positionField)
        
    def updatePosition(self, attr, control, valueField, positionField):
        value = cmds.floatField(positionField, query=True, value=True)
        
        values = cmds.gradientControlNoAttr( control, query=True, asString=True) 
        valuesSplit = values.split(',')
            
        current = cmds.gradientControlNoAttr( control, query=True, currentKey=True) 
        
        valuesSplit[current*3+1] = str(value)
        values = ",".join(valuesSplit)
        
        cmds.gradientControlNoAttr( control, edit=True, asString=values)
        self.syncAttribute(attr, control, valueField, positionField)
        
    def createRamp( self, attr ):
        #Create the control fields
        cmds.columnLayout( )
        
        cmds.rowLayout(nc=2, cw2=(142,220))
        cmds.text("Shutter Curve");
        cmds.text(" ");
        cmds.setParent('..')
        
        cmds.rowLayout("ShutterCurveRowLayout",nc=2, cw2=(142,220))
        
        cmds.columnLayout("ShutterCurveColumLayout")
        cmds.rowLayout("ShutterCurveValueLayout", nc=2, cw2=(60,45))
        cmds.text("Value");
        valueField = cmds.floatField("ShutterCurveValueField");
        cmds.setParent('..')
        
        cmds.rowLayout("ShutterCurvePositionLayout", nc=2, cw2=(60,45))
        cmds.text("Position");
        
        positionField = cmds.floatField("ShutterCurvePositionField");
        cmds.setParent('..')
        cmds.setParent('..')
        
        gradient = cmds.gradientControlNoAttr("ShutterCurveGradientControl", w=200, h=100 )
        cmds.gradientControlNoAttr( gradient, edit=True, changeCommand=lambda arg=None, x=attr, y=gradient, z=valueField, w=positionField:self.syncAttribute(x, y, z, w))
        
        #Initialize the curve with the values in the attribute
        curveString = ""
        attr = self.nodeAttr('aiShutterCurve')
        size = melUtils.getAttrNumElements(*attr.split('.', 1))
        startX = 0
        startY = 1
        if size > 0:
            x = cmds.getAttr(attr+'[0].aiShutterCurveX')
            y = cmds.getAttr(attr+'[0].aiShutterCurveY')
            startX = x
            startY = y
            curveString += str(y) +"," + str(x) +",1"
        else:
            curveString += "1,0,1"
        for i in range(1,size):
            x = cmds.getAttr(attr+'['+str(i)+'].aiShutterCurveX')
            y = cmds.getAttr(attr+'['+str(i)+'].aiShutterCurveY')
            curveString += ","+str(y) +"," + str(x) +",1"
            
        cmds.gradientControlNoAttr( gradient, edit=True, asString=curveString) 
        
        cmds.floatField(valueField, edit=True, value=startY, changeCommand=lambda arg=None, x=attr, y=gradient, z=valueField, w=positionField: self.updateValue(x, y, z, w))
        cmds.floatField(positionField, edit=True, value=startX, changeCommand=lambda arg=None, x=attr, y=gradient, z=valueField, w=positionField: self.updatePosition(x, y, z, w))
        
    def updateRamp( self, attr ):
        name = self.nodeName
        translator = cmds.getAttr(self.nodeAttr('aiTranslator'))

        uiParent = cmds.setParent( q = True )
        controls = cmds.columnLayout( uiParent, q=True, ca=True )
        
        curveString = ""
        attr = self.nodeAttr('aiShutterCurve')
        size = melUtils.getAttrNumElements(*attr.split('.', 1))
        if size > 0:
            x = cmds.getAttr(attr+'[0].aiShutterCurveX')
            y = cmds.getAttr(attr+'[0].aiShutterCurveY')
            curveString += str(y) +"," + str(x) +",1"
        else:
            curveString += "1,0,1"
        for i in range(1,size):
            x = cmds.getAttr(attr+'['+str(i)+'].aiShutterCurveX')
            y = cmds.getAttr(attr+'['+str(i)+'].aiShutterCurveY')
            curveString += ","+str(y) +"," + str(x) +",1"
            
        valuesSplit = curveString.split(",")
        
        if controls:
            for c in controls:
                control = c +"|ShutterCurveRowLayout|ShutterCurveGradientControl"
                valueField = c +"|ShutterCurveRowLayout|ShutterCurveColumLayout|ShutterCurveValueLayout|ShutterCurveValueField"
                positionField = c +"|ShutterCurveRowLayout|ShutterCurveColumLayout|ShutterCurvePositionLayout|ShutterCurvePositionField"
                cmds.gradientControlNoAttr( control, edit=True, asString=curveString)
                current = cmds.gradientControlNoAttr( control, query=True, currentKey=True) 
                
                cmds.floatField(valueField, edit=True, value=float(valuesSplit[current*3]))
                cmds.floatField(positionField, edit=True, value=float(valuesSplit[current*3+1]))

    
    def cameraFilterMapNew(self, nodeAttr):
        cmds.attrNavigationControlGrp('aiCameraFilterMap',
                                    label='Filtermap',
                                    at=nodeAttr, cn="createRenderNode -allWithShadersUp \"defaultNavigation -force true -connectToExisting -source %node -destination "+nodeAttr+"\" \"\"")

    def cameraFilterMapReplace(self, nodeAttr):
        cmds.attrNavigationControlGrp('aiCameraFilterMap', edit=True, at=nodeAttr, cn="createRenderNode -allWithShadersUp \"defaultNavigation -force true -connectToExisting -source %node -destination "+nodeAttr+"\" \"\"")

    def addCommonAttributes(self):
        self.addControl("aiExposure")
        self.addCustom('aiFiltermap', self.cameraFilterMapNew, self.cameraFilterMapReplace)

        self.addSeparator()
        self.addControl("aiRollingShutter")
        self.addControl("aiRollingShutterDuration")
        
    def addDOFAttributes(self):
        self.addSeparator()
        self.addControl("aiEnableDOF", label="Enable DOF")
        self.addControl("aiFocusDistance")
        self.addControl("aiApertureSize")
        self.addControl("aiApertureBlades")
        self.addControl("aiApertureBladeCurvature")
        self.addControl("aiApertureRotation")
        self.addControl("aiApertureAspectRatio")
        
    def globalShutterChanged(self, nodeAttr, *args):

        enabled = bool(cmds.getAttr(nodeAttr))

        cmds.attrFieldSliderGrp(self.aiShutterStartCtrl, edit=True, enable=not enabled)
        cmds.attrFieldSliderGrp(self.aiShutterEndCtrl, edit=True, enable=not enabled)

    def globalShutterNew(self, nodeAttr):

        cmds.setUITemplate('attributeEditorTemplate', pst=True)

        cmds.rowLayout(numberOfColumns=2)
        cmds.text(label="")
        cmds.checkBox('aiUseGlobalShutterCheckBox', label="Use Global Shutter")
        cmds.setParent('..')

        cmds.columnLayout(adj=True)
        self.aiShutterStartCtrl = cmds.attrFieldSliderGrp("aiShutterStartCtrl", label="Shutter Start", attribute='.'.join([self.nodeName, 'aiShutterStart']))
        self.aiShutterEndCtrl = cmds.attrFieldSliderGrp("aiShutterEndCtrl", label="Shutter End", attribute='.'.join([self.nodeName, 'aiShutterEnd']))
        cmds.setParent('..')

        cmds.setUITemplate(ppt=True)

        self.globalShutterReplace(nodeAttr)

    def globalShutterReplace(self, nodeAttr):
        cmds.connectControl('aiUseGlobalShutterCheckBox', '.'.join([self.nodeName, 'aiUseGlobalShutter']))
        cmds.attrFieldSliderGrp(self.aiShutterStartCtrl, edit=True, attribute='.'.join([self.nodeName, 'aiShutterStart']))
        cmds.attrFieldSliderGrp(self.aiShutterEndCtrl, edit=True, attribute='.'.join([self.nodeName, 'aiShutterEnd']))
        
        cmds.checkBox('aiUseGlobalShutterCheckBox', edit=True, changeCommand=lambda *args: self.globalShutterChanged(nodeAttr, *args))

        self.globalShutterChanged(nodeAttr)

    def addShutterAttributes(self):
        self.addSeparator()
        self.addControl("motionBlurOverride", label="Camera Motion Blur")

        self.beginNoOptimize()
        self.aiShutterStartCtrl = ""
        self.aiShutterEndCtrl = ""
        self.addCustom("aiUseGlobalShutter", self.globalShutterNew, self.globalShutterReplace)
        self.endNoOptimize()
        cmds.editorTemplate(suppress='aiShutterStart')
        cmds.editorTemplate(suppress='aiShutterEnd')
        self.addControl("aiShutterType")
        self.addCustom( "aiShutterCurve", self.createRamp, self.updateRamp )
        

class PerspCameraTemplate(CameraTemplate):
    def setup(self):
        self.addCommonAttributes()
        self.addDOFAttributes()
        self.addSeparator()
        self.addControl('aiUvRemap', label="UV Remap")
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl('aiRadialDistortion', label="Radial Distortion")
        self.addControl('aiRadialDistortionType', label="Radial Distortion Type")
        self.addControl('aiLensTiltAngle', label="Lens Tilt Angle")
        self.addControl('aiLensShift', label="Lens Shift")
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

templates.registerTranslatorUI(PerspCameraTemplate, "camera", "perspective")
templates.registerTranslatorUI(PerspCameraTemplate, "stereoRigCamera", "perspective")


class OrthographicTemplate(CameraTemplate):
    def setup(self):
        self.addCommonAttributes()
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

templates.registerTranslatorUI(OrthographicTemplate, "camera", "orthographic")
templates.registerTranslatorUI(OrthographicTemplate, "stereoRigCamera", "orthographic")

class FisheyeCameraTemplate(CameraTemplate):
    def setup(self):
        self.addCommonAttributes()
        self.addDOFAttributes()
        self.addSeparator()
        self.addControl('aiFov')
        self.addControl('aiAutocrop')
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

templates.registerTranslatorUI(FisheyeCameraTemplate, "camera", "fisheye")
templates.registerTranslatorUI(FisheyeCameraTemplate, "stereoRigCamera", "fisheye")

class CylCameraTemplate(CameraTemplate):
    def setup(self):
        self.addCommonAttributes()
        self.addControl('aiHorizontalFov')
        self.addControl('aiVerticalFov')
        self.addControl('aiProjective')
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

templates.registerTranslatorUI(CylCameraTemplate, "camera", "cylindrical")
templates.registerTranslatorUI(CylCameraTemplate, "stereoRigCamera", "cylindrical")

class SphericalCameraTemplate(CameraTemplate):
    def setup(self):
        self.addCommonAttributes()
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")

templates.registerTranslatorUI(SphericalCameraTemplate, "camera", "spherical")
templates.registerTranslatorUI(SphericalCameraTemplate, "stereoRigCamera", "spherical")

class VrCameraTemplate(CameraTemplate):
    def setup(self):
        self.beginLayout("Main Attributes", collapse=False)
        self.addControl("aiMode")
        self.addControl("aiProjection")
        self.addControl("aiEyeSeparation")
        self.addControl("aiEyeToNeck")
        self.endLayout()

        self.beginLayout("Pole Merging", collapse=False)
        self.addControl("aiTopMergeMode", label = "Top Merge Mode *") # Not supported in GPU
        self.addControl("aiTopMergeAngle")
        self.addSeparator()
        self.addControl("aiBottomMergeMode", label = "Bottm Merge Mode *") # Not supported in GPU
        self.addControl("aiBottomMergeAngle")
        self.addSeparator()
        self.addControl("aiMergeShader")
        self.endLayout()

        self.beginLayout("Common Attributes", collapse=False)
        self.addCommonAttributes()
        self.addShutterAttributes()
        self.addSeparator()
        self.addControl("aiUserOptions", label="User Options")
        self.endLayout()

templates.registerTranslatorUI(VrCameraTemplate, "camera", "vr_camera")

def cameraOrthographicChanged(orthoPlug, *args):
    # Bool attribute orthographic is being changed, we must eventually
    # sync the translator attributeq
    if not core.arnoldIsCurrentRenderer(): return
    fnCam = om.MFnCamera(orthoPlug.node())
    transPlug = fnCam.findPlug('aiTranslator')
    # if not transPlug.isNull():
    isOrtho = orthoPlug.asBool()
    currTrans = transPlug.asString()
    nodeName = fnCam.fullPathName()
    if not currTrans:
        # if the current translator is not set get the default for this node
        currTrans = core.getDefaultTranslator(nodeName)
    newTrans = None
    if isOrtho:
        # We're setting orthographic checkbox to True, but the 
        # current translator is known to be perspective. Let's
        # switch to orthographic translator
        if currTrans == 'perspective' or currTrans == 'fisheye' or not currTrans:
            newTrans = 'orthographic'
    else:
        # We're disabling the orthographic checkbox, but the 
        # current translator is still orthographic. Let's switch it 
        # to perspective
        if currTrans == 'orthographic':
            newTrans = 'perspective'
    if newTrans:
        transPlug.setString(newTrans)

def cameraTranslatorChanged(transPlug, *args):
    # The translator attribute is being changed, we must eventually
    # update the bool attribute "orthographic"
    if not core.arnoldIsCurrentRenderer(): return
    fnCam = om.MFnCamera(transPlug.node())
    currTrans = transPlug.asString()
    nodeName = fnCam.fullPathName()
    if not currTrans:
        # if the current translator is not set get the default for this node
        currTrans = core.getDefaultTranslator(nodeName)
    orthoPlug = fnCam.findPlug('orthographic')
    isOrtho = orthoPlug.asBool()
    builtinPerspectiveCams = ['perspective', 'fisheye', 'cylindrical', 'spherical', 'vr_camera']
    if currTrans == 'orthographic':
        # We're setting the translator to orthographic,
        # we need to enable the checkbox "orthographic"
        if not isOrtho:
            orthoPlug.setBool(True)
    elif currTrans in builtinPerspectiveCams:
        # We're setting the translator to a perspective camera,
        # we must ensure the checkbox "orthographic" is disabled
        if isOrtho:
            orthoPlug.setBool(False)
    else:
        # Unknown camera ! Let's check for metadata "orthographic"
        cameraNodes = cmds.arnoldPlugins(listMatchMetadata=("maya.name", "camera"))
        matchingTranslators = cmds.arnoldPlugins(listMatchMetadata=("maya.translator", currTrans))
        # we need to find a camera node that has the matching translator
        camera = None
        for cameraNode in cameraNodes:
            if cameraNode in matchingTranslators:
                camera = cameraNode
                break

        if camera:
            # we found the translator, let's ask for its metadata "orthographic"
            newOrtho = False
            orthoMetadata = cmds.arnoldPlugins(getNodeMetadata=(camera, 'orthographic'))
            if orthoMetadata == 'true':
                newOrtho = True

            if isOrtho != newOrtho:
                orthoPlug.setBool(newOrtho)


def getCameraDefault(obj):
    isOrtho = om.MFnDependencyNode(obj).findPlug("orthographic").asBool()
    default = 'orthographic' if isOrtho else 'perspective'
    return default

callbacks.addAttributeChangedCallbacks('camera',
                                       [('aiTranslator', cameraTranslatorChanged),
                                        ('orthographic', cameraOrthographicChanged)])

callbacks.addAttributeChangedCallbacks('stereoRigCamera',
                                       [('aiTranslator', cameraTranslatorChanged),
                                        ('orthographic', cameraOrthographicChanged)])

def registerDriverTemplates():
    skipDrivers = ['exr', 'deepexr']
    # register driver templates
    for transName, arnoldNode in core.listTranslators("aiAOVDriver"):
        if not (transName in skipDrivers): # we want to use a custom ui for the EXR translator
            templates.registerAutoTranslatorUI(arnoldNode, "aiAOVDriver", transName, skipEmpty=True)

    templates.registerDefaultTranslator('aiAOVDriver', 'exr')

templatesNames = []
    
class EXRDriverTranslatorUI(templates.AttributeTemplate):

    def selectedAttrName(self, nodeName):
        # unfortunately the functions below might be called 
        # with NodeNames corresponding to a previously selected node
        # (maya doesn't rebuild the UI but just updates the param values so the callbacks remain)
        tokens = nodeName.split('.')

        # same object, nothing to change
        if (tokens[0] == self.nodeName):
            return nodeName

        #replace the first token by the actual self name
        result = self.nodeName
        tokens.pop(0)
        
        for token in tokens:
            result += "." + token

        return result


    def changeAttrName(self, nodeName, attrNameText, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        # Get the new name
        name = cmds.textField(attrNameText, query=True, text=True)
        
        # Update the name in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.textField(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeName", edit=True, text=name.replace(" ", ""))
        
        # Update the metadata value
        metadata = result[0]+" "+name.replace(" ", "")+" "+result[2]
        cmds.setAttr(attrName, metadata, type="string")
    
    def changeAttrType(self, nodeName, menu, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        # Get the new type
        typeNumber = cmds.optionMenu(menu, query=True, select=True)
        type = cmds.optionMenu(menu, query=True, value=True)
        
        # Update the type in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.optionMenu(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeType", edit=True, select=typeNumber)
            
        # Update the metadata value
        metadata = type+" "+result[1]+" "+result[2]
        cmds.setAttr(attrName, metadata, type="string")
        
    def changeAttrValue(self, nodeName, attrValueText, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))

        # Get the new value
        value = cmds.textField(attrValueText, query=True, text=True)
        
        # Update the value in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.textField(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeValue", edit=True, text=value)
        
        # Update the metadata value
        metadata = result[0]+" "+result[1]+" "+value
        cmds.setAttr(attrName, metadata, type="string")
        
    def removeAttribute(self, nodeName, index):
        nodeName = self.selectedAttrName(nodeName)
        cmds.removeMultiInstance(nodeName+'['+str(index)+']')
        self.updatedMetadata(nodeName)
        
    def addAttribute(self, nodeName):
        
        nodeName = self.selectedAttrName(nodeName)
        next = 0
        if cmds.getAttr(nodeName, multiIndices=True):
            next = cmds.getAttr(nodeName, multiIndices=True)[-1] + 1
        cmds.setAttr(nodeName+'['+str(next)+']', "INT", type="string")
        self.updatedMetadata(nodeName)
        
    def updateLine(self, nodeName, metadata, index):
        # Attribute controls will be created with the current metadata content
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        nodeName = self.selectedAttrName(nodeName)
        # Attribute Name
        attrNameText = cmds.textField("MtoA_exrMAttributeName", text=result[1])
        cmds.textField(attrNameText, edit=True, changeCommand=lambda arg=None, x=nodeName, y=attrNameText, z=index: self.changeAttrName(x, y, z))
        
        # Attribute Type
        menu = cmds.optionMenu("MtoA_exrMAttributeType")
        cmds.menuItem( label='INT', data=0)
        cmds.menuItem( label='FLOAT', data=1)
        cmds.menuItem( label='VECTOR2', data=2)
        cmds.menuItem( label='MATRIX', data=3)
        cmds.menuItem( label='STRING', data=4)
        if result[0] == 'INT':
            cmds.optionMenu(menu, edit=True, select=1)
        elif result[0] == 'FLOAT':
            cmds.optionMenu(menu, edit=True, select=2)
        elif result[0] == 'VECTOR2':
            cmds.optionMenu(menu, edit=True, select=3)
        elif result[0] == 'MATRIX':
            cmds.optionMenu(menu, edit=True, select=4)
        elif result[0] == 'STRING':
            cmds.optionMenu(menu, edit=True, select=5)
        cmds.optionMenu(menu, edit=True, changeCommand=lambda arg=None, x=nodeName, y=menu, z=index: self.changeAttrType(x, y, z))
        
        # Attribute Value
        attrValueText = cmds.textField("MtoA_exrMAttributeValue", text=result[2])
        cmds.textField(attrValueText, edit=True, changeCommand=lambda arg=None, x=nodeName, y=attrValueText, z=index: self.changeAttrValue(x, y, z))
        
        # Remove button
        cmds.symbolButton(image="SP_TrashIcon.png", command=lambda arg=None, x=nodeName, y=index:self.removeAttribute(x, y))
        
    def updatedMetadata(self, nodeName):

        nodeName = self.selectedAttrName(nodeName)

        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.setParent(templateName)
            #Remove all attributes controls and rebuild them again with the metadata updated content
            for child in cmds.columnLayout(templateName, query=True, childArray=True) or []:
                cmds.deleteUI(child)
            for index in cmds.getAttr(nodeName, multiIndices=True) or []:
                attrName = nodeName+'['+str(index)+']'
                metadata = cmds.getAttr(attrName)
                if metadata:
                    cmds.rowLayout('mtoa_exrMetadataRow_'+str(index),nc=4, cw4=(120,80,120,20), cl4=('center', 'center', 'center', 'right'))
                    self.updateLine(nodeName, metadata, index)
                    cmds.setParent('..')
        
    def metadataNew(self, nodeName):
        cmds.rowLayout(nc=2, cw2=(200,140), cl2=('center', 'center'))

        nodeName = self.selectedAttrName(nodeName)
        cmds.button( label='Add New Attribute', command=lambda arg=None, x=nodeName: self.addAttribute(x))
        cmds.setParent( '..' )
        layout = cmds.columnLayout(rowSpacing=5, columnWidth=340)
        # This template could be created more than once in different panels
        templatesNames.append(layout)
        self.updatedMetadata(nodeName)
        cmds.setParent( '..' )

    def metadataReplace(self, nodeName):
        pass

    def setup(self):
        self.addControl('exrCompression', label='Compression')
        self.addControl('halfPrecision', label='Half Precision')
        self.addControl('preserveLayerName', label='Preserve Layer Name')
        self.addControl('exrTiled', label='Tiled')
        self.addControl('multipart', label='Multipart')
        self.addControl('autocrop', label='Autocrop')
        self.addControl('append', label='Append')
        self.addControl('mergeAOVs', label='Merge AOVs')
        self.beginLayout("Metadata (name, type, value)", collapse=True)
        self.addCustom('custom_attributes', self.metadataNew, self.metadataReplace)
        self.endLayout()

templates.registerTranslatorUI(EXRDriverTranslatorUI, 'aiAOVDriver', 'exr')


deepexrToleranceTemplates = []
deepexrHalfPrecisionTemplates = []
deepexrEnableFilteringTemplates = []

class DeepEXRDriverTranslatorUI(templates.AttributeTemplate):
    def __init__(self, nodeType):
        
        aovs.addAOVChangedCallback(self.updateLayerTolerance, 'DeepEXRDriverTranslatorUITolerance')
        aovs.addAOVChangedCallback(self.updateLayerHalfPrecision, 'DeepEXRDriverTranslatorUIHalfPrecision')
        aovs.addAOVChangedCallback(self.updateLayerEnableFiltering, 'DeepEXRDriverTranslatorUIEnableFiltering')
        super(DeepEXRDriverTranslatorUI, self).__init__(nodeType)

    def selectedAttrName(self, nodeName):
        # unfortunately the functions below might be called 
        # with NodeNames corresponding to a previously selected node
        # (maya doesn't rebuild the UI but just updates the param values so the callbacks remain)
        tokens = nodeName.split('.')

        # same object, nothing to change
        if (tokens[0] == self.nodeName):
            return nodeName

        #replace the first token by the actual self name
        result = self.nodeName
        tokens.pop(0)

        for token in tokens:
            result += "." + token

        return result

    def updateLayerTolerance(self):

        aovList = aovs.getAOVs(enabled=True)

        deepexrToleranceTemplates[:] = [tup for tup in deepexrToleranceTemplates if cmds.columnLayout(tup, exists=True)]
        for templateName in deepexrToleranceTemplates:
            
            driverName = self.nodeName
            if not cmds.objExists(driverName):
                continue

            # note that this function may be called to fill the defaultArnoldDriver exposed params
            # but with self != defaultArnoldDriver
            # this is because we might want to add this aov name in the default layers* list
            
            # in the render settings window I only want to display the defaultArnoldDriver params
            if templateName[:26] == "unifiedRenderGlobalsWindow":
                driverName = "defaultArnoldDriver"

            cmds.setParent(templateName)
            for child in cmds.columnLayout(templateName, query=True, childArray=True) or []:
                cmds.deleteUI(child)
                
            cmds.attrFieldSliderGrp(label='alpha' , at=driverName + '.alphaTolerance' )
            cmds.attrFieldSliderGrp(label='depth' , at=driverName + '.depthTolerance' )

            if driverName == "defaultArnoldDriver":
                cmds.attrFieldSliderGrp(label='beauty' , at='defaultArnoldDriver.layerTolerance[0]')
                for i in range(0,len(aovList)):
                    driver_list = cmds.listConnections('{}.outputs[0].driver'.format(aovList[i].node), source=True, destination=False)
                    if driver_list and len(driver_list) and driver_list[0] == 'defaultArnoldDriver':
                        labelStr = aovList[i].name
                        attrStr = 'defaultArnoldDriver.layerTolerance['+str(i+1)+']'
                        cmds.attrFieldSliderGrp(label=labelStr , at=attrStr )
            else:
                cmds.attrFieldSliderGrp(label='layer' , at=driverName + '.layerTolerance[0]' )
            
    def updateLayerHalfPrecision(self):
        aovList = aovs.getAOVs(enabled=True)
        
        deepexrHalfPrecisionTemplates[:] = [tup for tup in deepexrHalfPrecisionTemplates if cmds.columnLayout(tup, exists=True)]
        for templateName in deepexrHalfPrecisionTemplates:
            cmds.setParent(templateName)

            driverName = self.nodeName
            if not cmds.objExists(driverName):
                continue

            # note that this function may be called to fill the defaultArnoldDriver exposed params
            # but with self != defaultArnoldDriver
            # this is because we might want to add this aov name in the default layers* list
            
            # in the render settings window I only want to display the defaultArnoldDriver params
            if templateName[:26] == "unifiedRenderGlobalsWindow":
                driverName = "defaultArnoldDriver"

            for child in cmds.columnLayout(templateName, query=True, childArray=True) or []:
                cmds.deleteUI(child)

            cmds.attrControlGrp(label='alpha' , a=driverName+'.alphaHalfPrecision' )
            cmds.attrControlGrp(label='depth' , a=driverName+'.depthHalfPrecision' )

            if driverName == "defaultArnoldDriver":
                cmds.attrControlGrp(label='beauty' , a='defaultArnoldDriver.layerHalfPrecision[0]' )

                for i in range(0,len(aovList)):
                    driver_list = cmds.listConnections('{}.outputs[0].driver'.format(aovList[i].node), source=True, destination=False)
                    if driver_list and len(driver_list) and driver_list[0] == 'defaultArnoldDriver':
                        labelStr = aovList[i].name
                        attrStr = 'defaultArnoldDriver.layerHalfPrecision['+str(i+1)+']'
                        cmds.attrControlGrp(label=labelStr , a=attrStr )
            else:
                cmds.attrControlGrp(label='layer' , a=driverName+'.layerHalfPrecision[0]' )


    def updateLayerEnableFiltering(self):
        aovList = aovs.getAOVs(enabled=True)
        
        deepexrEnableFilteringTemplates[:] = [tup for tup in deepexrEnableFilteringTemplates if cmds.columnLayout(tup, exists=True)]
        for templateName in deepexrEnableFilteringTemplates:
            cmds.setParent(templateName)

            # note that this function may be called to fill the defaultArnoldDriver exposed params
            # but with self != defaultArnoldDriver
            # this is because we might want to add this aov name in the default layers* list
            
            # in the render settings window I only want to display the defaultArnoldDriver params
            driverName = self.nodeName
            if not cmds.objExists(driverName):
                continue

            if templateName[:26] == "unifiedRenderGlobalsWindow":
                driverName = "defaultArnoldDriver"

            for child in cmds.columnLayout(templateName, query=True, childArray=True) or []:
                cmds.deleteUI(child)

            if driverName == "defaultArnoldDriver":
                cmds.attrControlGrp(label='beauty' , a='defaultArnoldDriver.layerEnableFiltering[0]' )
                for i in range(0,len(aovList)):
                    driver_list = cmds.listConnections('{}.outputs[0].driver'.format(aovList[i].node), source=True, destination=False)
                    if driver_list and len(driver_list) and driver_list[0] == 'defaultArnoldDriver':
                        labelStr = aovList[i].name
                        attrStr = 'defaultArnoldDriver.layerEnableFiltering['+str(i+1)+']'
                        cmds.attrControlGrp(label=labelStr , a=attrStr )
            else:
                cmds.attrControlGrp(label='layer' , a=driverName +'.layerEnableFiltering[0]' )
     
    def layerToleranceNew(self, nodeName):
        layout = cmds.columnLayout(rowSpacing=5, columnWidth=340)
        deepexrToleranceTemplates.append(layout)
        
        self.updateLayerTolerance()
        cmds.setParent( '..' )
        
    def layerToleranceReplace(self, nodeName):

        self.updateLayerTolerance()
        cmds.setParent( '..' )
        
    def layerHalfPrecisionNew(self, nodeName):
        layout = cmds.columnLayout(rowSpacing=5, columnWidth=340)
        deepexrHalfPrecisionTemplates.append(layout)
        self.updateLayerHalfPrecision()
        cmds.setParent( '..' )
        
    def layerHalfPrecisionReplace(self, nodeName):
        self.updateLayerHalfPrecision()
        cmds.setParent( '..' )
        
    def layerEnableFilteringNew(self, nodeName):
        layout = cmds.columnLayout(rowSpacing=5, columnWidth=340)
        deepexrEnableFilteringTemplates.append(layout)
        self.updateLayerEnableFiltering()
        cmds.setParent( '..' )
        
    def layerEnableFilteringReplace(self, nodeName):
        self.updateLayerEnableFiltering()
        cmds.setParent( '..' )

    def changeAttrName(self, nodeName, attrNameText, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        # Get the new name
        name = cmds.textField(attrNameText, query=True, text=True)
        
        # Update the name in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.textField(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeName", edit=True, text=name.replace(" ", ""))
        
        # Update the metadata value
        metadata = result[0]+" "+name.replace(" ", "")+" "+result[2]
        cmds.setAttr(attrName, metadata, type="string")
    
    def changeAttrType(self, nodeName, menu, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        # Get the new type
        typeNumber = cmds.optionMenu(menu, query=True, select=True)
        type = cmds.optionMenu(menu, query=True, value=True)
        
        # Update the type in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.optionMenu(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeType", edit=True, select=typeNumber)
            
        # Update the metadata value
        metadata = type+" "+result[1]+" "+result[2]
        cmds.setAttr(attrName, metadata, type="string")
        
    def changeAttrValue(self, nodeName, attrValueText, index):
        # Get the attribute name, type and value
        attrName = nodeName+'['+str(index)+']'
        metadata = cmds.getAttr(attrName)
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))

        # Get the new value
        value = cmds.textField(attrValueText, query=True, text=True)
        
        # Update the value in all the templates
        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.textField(templateName+"|mtoa_exrMetadataRow_"+str(index)+"|MtoA_exrMAttributeValue", edit=True, text=value)
        
        # Update the metadata value
        metadata = result[0]+" "+result[1]+" "+value
        cmds.setAttr(attrName, metadata, type="string")
        
    def removeAttribute(self, nodeName, index):
        nodeName = self.selectedAttrName(nodeName)
        cmds.removeMultiInstance(nodeName+'['+str(index)+']')
        self.updatedMetadata(nodeName)
        
    def addAttribute(self, nodeName):

        nodeName = self.selectedAttrName(nodeName)
        next = 0
        if cmds.getAttr(nodeName, multiIndices=True):
            next = cmds.getAttr(nodeName, multiIndices=True)[-1] + 1
        cmds.setAttr(nodeName+'['+str(next)+']', "INT", type="string")
        self.updatedMetadata(nodeName)
        
    def updateLine(self, nodeName, metadata, index):
        # Attribute controls will be created with the current metadata content
        result = metadata.split(' ', 2 )
        result += [""] * (3-len(result))
        
        nodeName = self.selectedAttrName(nodeName)

        # Attribute Name
        attrNameText = cmds.textField("MtoA_exrMAttributeName", text=result[1])
        cmds.textField(attrNameText, edit=True, changeCommand=lambda arg=None, x=nodeName, y=attrNameText, z=index: self.changeAttrName(x, y, z))
        
        # Attribute Type
        menu = cmds.optionMenu("MtoA_exrMAttributeType")
        cmds.menuItem( label='INT', data=0)
        cmds.menuItem( label='FLOAT', data=1)
        cmds.menuItem( label='VECTOR2', data=2)
        cmds.menuItem( label='MATRIX', data=3)
        cmds.menuItem( label='STRING', data=4)
        if result[0] == 'INT':
            cmds.optionMenu(menu, edit=True, select=1)
        elif result[0] == 'FLOAT':
            cmds.optionMenu(menu, edit=True, select=2)
        elif result[0] == 'VECTOR2':
            cmds.optionMenu(menu, edit=True, select=3)
        elif result[0] == 'MATRIX':
            cmds.optionMenu(menu, edit=True, select=4)
        elif result[0] == 'STRING':
            cmds.optionMenu(menu, edit=True, select=5)
        cmds.optionMenu(menu, edit=True, changeCommand=lambda arg=None, x=nodeName, y=menu, z=index: self.changeAttrType(x, y, z))
        
        # Attribute Value
        attrValueText = cmds.textField("MtoA_exrMAttributeValue", text=result[2])
        cmds.textField(attrValueText, edit=True, changeCommand=lambda arg=None, x=nodeName, y= attrValueText, z=index: self.changeAttrValue(x, y, z))
        
        # Remove button
        cmds.symbolButton(image="SP_TrashIcon.png", command=lambda arg=None, x=nodeName, y=index: self.removeAttribute(x, y))
        
    def updatedMetadata(self, nodeName):
        
        nodeName = self.selectedAttrName(nodeName)

        templatesNames[:] = [tup for tup in templatesNames if cmds.columnLayout(tup, exists=True)]
        for templateName in templatesNames:
            cmds.setParent(templateName)
            #Remove all attributes controls and rebuild them again with the metadata updated content
            for child in cmds.columnLayout(templateName, query=True, childArray=True) or []:
                cmds.deleteUI(child)
            for index in cmds.getAttr(nodeName, multiIndices=True) or []:
                attrName = nodeName+'['+str(index)+']'
                metadata = cmds.getAttr(attrName)
                if metadata:
                    cmds.rowLayout('mtoa_exrMetadataRow_'+str(index),nc=4, cw4=(120,80,120,20), cl4=('center', 'center', 'center', 'right'))
                    self.updateLine(nodeName, metadata, index)
                    cmds.setParent('..')
        
    def metadataNew(self, nodeName):

        nodeName = self.selectedAttrName(nodeName)

        cmds.rowLayout(nc=2, cw2=(200,140), cl2=('center', 'center'))
        cmds.button( label='Add New Attribute', command=lambda arg=None, x=nodeName: self.addAttribute(x))
        cmds.setParent( '..' )
        layout = cmds.columnLayout(rowSpacing=5, columnWidth=340)
        # This template could be created more than once in different panels
        templatesNames.append(layout)
        self.updatedMetadata(nodeName)
        cmds.setParent( '..' )

    def metadataReplace(self, nodeName):
        pass
        
    def setup(self):
        self.addControl('deepexrTiled', label='Tiled')
        self.addControl('append', label='Append')
        self.addControl('mergeAOVs', label='Merge AOVs')
        self.addControl('subpixelMerge', label='Subpixel Merge')
        self.addControl('useRGBOpacity', label='Use RGB Opacity')
        self.beginLayout("Tolerance Values", collapse=False)
        self.addCustom('layerToleranceSection', self.layerToleranceNew, self.layerToleranceReplace)
        self.endLayout()
        
        self.beginLayout("Half Precision", collapse=False)
        self.addCustom('layerHalfPrecisionSection', self.layerHalfPrecisionNew, self.layerHalfPrecisionReplace)
        self.endLayout()
        
        self.beginLayout("Enable Filtering", collapse=False)
        self.addCustom('layerEnableFilteringSection', self.layerEnableFilteringNew, self.layerEnableFilteringReplace)
        self.endLayout()
        
        self.beginLayout("Metadata (name, type, value)", collapse=True)
        self.addCustom('custom_attributes', self.metadataNew, self.metadataReplace)
        self.endLayout()

        

templates.registerTranslatorUI(DeepEXRDriverTranslatorUI, 'aiAOVDriver', 'deepexr')

def registerFilterTemplates():
    # register driver templates
    for transName, arnoldNode in core.listTranslators("aiAOVFilter"):
        templates.registerAutoTranslatorUI(arnoldNode, "aiAOVFilter", transName, skipEmpty=True)

    templates.registerDefaultTranslator('aiAOVFilter', 'gaussian')

registerDriverTemplates()
registerFilterTemplates()
